在開始之前,我們要再做一些詞彙的釐清:
要對 Datomic 資料庫做寫入,首先第一件事,必須先定義好綱要 (schema)。與 SQL 資料庫很大的一點不同是:「Datomic 並沒有專用的 DDL (Data Definition Language),因為 Datomic 的綱要也是資料,所以用一般的語法寫入即可。」
那該怎麼定義網要呢?這就要用到 Datomic 的內建屬性。此外,每一個屬性,都有三個必要的內建屬性。
比方說,下方我們定義了一個 :movie/title
的綱要,這個 :movie/title
之後就可以拿來當綱要使用。在定義它的時候,我們要指定三個必要的內建屬性:
:db/ident
定義它的名稱。:db/valueType
定義它的資料型態。 :db/cardinality
定義它所對應的值是單一又或是多數。上述三個 :db/ident
, :db/valueType
, :db/cardinality
就是三個必要的內建屬性。而 :db/doc
是可以選配的內建屬性。讀者可以發現,內建屬性的命名有一致性,一定會是 :db/$$$$
的型式,這個 :db
的命名空間自然也是保留給內建屬性使用的。
{:db/ident :movie/title
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one
:db/doc "The title of the movie"}
:db/valueType
有哪些資料型態可以指定下列都是可以用的資料型態:
(':db.type/bigdec' | ':db.type/bigint' | ':db.type/boolean' |
':db.type/bytes' | ':db.type/double' | ':db.type/float' |
':db.type/instant' | ':db.type/keyword' | ':db.type/long' |
':db.type/ref' | ':db.type/string' | ':db.type/symbol' |
':db.type/tuple' | ':db.type/uuid' | ':db.type/uri')
其中,有 8 個比較常用:
:db.type/instant
:表示時間,對應 java.util.Date
:db.type/uuid
: UUID,對應 java.util.UUID
:db.type/ref
:表示指標,可以指向別的資料實體 (entity):db.type/string'
:字串,對應 java.lang.String
:db.type/long
:長整數:db.type/float
:浮點數 (float):db.type/double
:浮點數 (double):db.type/boolean
:布林值特別注意一點::db.type/bytes
這個資料型態因為有設計上的考慮不周,已經被棄用了。
這邊來看一個範例,先定義電影的屬性,然後寫入屬性、寫入電影的資料。
(def movie-schema [{:db/ident :movie/title
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one
:db/doc "The title of the movie"}
{:db/ident :movie/genre
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one
:db/doc "The genre of the movie"}
{:db/ident :movie/release-year
:db/valueType :db.type/long
:db/cardinality :db.cardinality/one
:db/doc "The year the movie was released in theaters"}])
這邊,定義了三個自訂屬性::movie/title
、:movie/genra
、:movie/release-year
。
定義好了之後,我們就可以利用這三個自訂屬性來描述想放入資料庫的資料了。
@(d/transact conn movie-schema)
在這邊,conn
是資料庫的連線,d/transact
是寫入資料庫的 API ,而 movie-schema
則是前面定義好的屬性。
(def first-movies [{:movie/title "The Goonies"
:movie/genre "action/adventure"
:movie/release-year 1985}
{:movie/title "Commando"
:movie/genre "action/adventure"
:movie/release-year 1985}
{:movie/title "Repo Man"
:movie/genre "punk dystopia"
:movie/release-year 1984}])
@(d/transact conn first-movies)
在這邊,first-movies
這個變數對應了電影的資料,仔細看,這邊寫入了三部電影,每部電影都恰好用三個之前定義好的自訂屬性加以描述。
註:從這邊開始,讀者如果去看官方文件時,可能會注意到 Datomic 有三個不同的版本:Datomic Cloud, Datomic Pro, Datomic Local。此外,Datomic 的 API 也有 Peer API 與 Client API 兩種。要解釋完差異與該如何選擇,故事有點長,已超出本系列文的範疇。本系列文只專注於 Datomic Pro 與 Peer API 。
Datomic 寫入資料庫 API d/transact
的第二個引數,它是 vector of tx-data 。讀者可以想成,交易資料 (tx-data) 是最小單位,它可以對應到一個又一個的資料原子 (datom)。而我們在寫入資料時,通常還是會一口氣寫入多個資料原子,所以 API 的第二個引數會是 vector of tx-data。
交易資料 (tx-data) 有兩種形式: map 形式或是 list 形式。兩種形式是等價的,之前的範例是 map 形式,map 形式是 list 形式的語法糖而已。所以我們要深入了解交易資料,還是要從 list 形式來著手。
先看 list 形式的交易資料的基本範例:
[:db/add entity-id attribute value]
[:db/retract entity-id attribute value?]
:db/id
[{:db/id item-1-id
:line-item/product chocolate
:line-item/quantity 1}
{:db/id item-2-id
:line-item/product whisky
:line-item/quantity 2}]
[[:db/add item-1-id :line-item/product chocolate]
[:db/add item-1-id :line-item/quantity 1]
[:db/add item-2-id :line-item/product whisky]
[:db/add item-2-id :line-item/quantity 2]]
這邊有一個很容易讓人困惑的:「當一筆資料還沒有寫入資料庫時,我們無法得知它對應的資料實體編碼 (entity id) 啊?那該怎麼填入類似上方例子裡的 item-1-id
與 item-2-id
呢?」
答案很簡單,就用一個臨時字串就好了,比方說:"temp-item-1"
。而這邊的臨時字串在 Datomic 的官方文件又稱之為暫時編碼 (Tempids)。
先來探討一下 SQL 資料庫的交易 (transaction) 語意。
多數的 SQL 資料庫系統由一系列更新組成交易,其中每個更新都會更改資料庫的狀態。根據所提供的隔離程度,交易也可能會看到來自其他交易的交錯更新,例如 a'
甚至可能不等於 b
:
BEGIN TRANSACTION
UPDATE ... ;; db state a -> db state a'
UPDATE ... ;; db state b -> db state b'
UPDATE ... ;; db state c -> db state c'
COMMIT
然而,Datomic 並非如此。Datomic 的交易定義為將一組資料原子 (datoms) 新增至資料庫。